#pragma once

#include "zen_matrix.h"
#include "zen_wrap.h"

#define __attribute_float3(Name)   \
	ConstPoint3& Get##Name()       \
	{                              \
		return m##Name;            \
	}                              \
	float Get##Name##X()           \
	{                              \
		return m##Name.x;          \
	}                              \
	float Get##Name##Y()           \
	{                              \
		return m##Name.y;          \
	}                              \
	float Get##Name##Z()           \
	{                              \
		return m##Name.z;          \
	}                              \
	void Set##Name(ConstPoint3& v) \
	{                              \
		m##Name = v;               \
		SetMatrixDirty(true);      \
	}                              \
	void Set##Name##X(float v)     \
	{                              \
		m##Name.x = v;             \
		SetMatrixDirty(true);      \
	}                              \
	void Set##Name##Y(float v)     \
	{                              \
		m##Name.y = v;             \
		SetMatrixDirty(true);      \
	}                              \
	void Set##Name##Z(float v)     \
	{                              \
		m##Name.z = v;             \
		SetMatrixDirty(true);      \
	}                              \
	Point3 m##Name

#define __attribute_float2(Name)   \
	ConstPoint2& Get##Name()       \
	{                              \
		return m##Name;            \
	}                              \
	float Get##Name##X()           \
	{                              \
		return m##Name.x;          \
	}                              \
	float Get##Name##Y()           \
	{                              \
		return m##Name.y;          \
	}                              \
	void Set##Name(ConstPoint2& v) \
	{                              \
		m##Name = v;               \
		SetMatrixDirty(true);      \
	}                              \
	void Set##Name##X(float v)     \
	{                              \
		m##Name.x = v;             \
		SetMatrixDirty(true);      \
	}                              \
	void Set##Name##Y(float v)     \
	{                              \
		m##Name.y = v;             \
		SetMatrixDirty(true);      \
	}                              \
	Point2 m##Name

#define __attribute_float1(Name) \
	float Get##Name()            \
	{                            \
		return m##Name;          \
	}                            \
	void Set##Name(float v)      \
	{                            \
		m##Name = v;             \
		SetMatrixDirty(true);    \
	}                            \
	float m##Name

/*!
 @header zen_game_node.h
 @abstract basic class node and the part-class.
 @author mehertj
 @version v1.0
 */
namespace Zen
{
	namespace Game
	{
		class Node;

		class NodeWithMatrix
		{
		public:
			virtual ~NodeWithMatrix() = default;

			Mat4 const& GetMatrix()
			{
				return mMatrix;
			}
			int GetMatrixVersion()
			{
				return mMatrixVersion;
			}
			void SetMatrix(Mat4 const& v)
			{
				mMatrix = v;
				++mMatrixVersion;
			}

		protected:
			int mMatrixVersion = 0;
			Mat4 mMatrix{};
		};

		class Transform2D_
		{
		public:
			__attribute_float2(Position){};
			__attribute_float2(Scale){ 1.f, 1.f };
			__attribute_float2(Skew){ 1.f, 1.f };
			__attribute_float2(Anchor){};
			__attribute_float1(Rotation){};
			__attribute_float2(Size){ 1.f, 1.f };

		protected:
			void SetMatrixDirty(bool dirty);
			Mat4 Transform();
			bool mIsTransformDirty = true;
		};

		class Transform3D_
		{
		public:
			__attribute_float1(RotationAngle) = 0;
			__attribute_float3(Position){};
			__attribute_float3(Scale){ 1.f, 1.f };
			__attribute_float3(Anchor){};
			__attribute_float3(Size){ 1.f, 1.f };
			__attribute_float3(RotationAxis){ 0.f, 0.f, 1.f };

		protected:
			void SetMatrixDirty(bool dirty);
			Mat4 Transform();
			bool mIsTransformDirty = true;
		};

		template <typename Type>
		class NodeWithMatrix_ : public Type, public NodeWithMatrix
		{
		public:
			void UpdateMatrix()
			{
				if (Type::mIsTransformDirty)
				{
					mMatrix = Type::Transform();
					Type::mIsTransformDirty = false;
				}
			}
		};

		template <typename Type>
		class NodeWithOverMatrix_ : public Type, public NodeWithMatrix
		{
		public:
			void UpdateOverMatrix(Mat4 const* top, int version)
			{
				if (!top)
				{
					if (Type::mIsTransformDirty)
					{
						mMatrix = Type::Transform();
						Type::mIsTransformDirty = false;
					}
				}
				else
				{
					if (Type::mIsTransformDirty)
					{
						mInnerMatrix = Type::Transform();
						mMatrix = *top & mInnerMatrix;
						Type::mIsTransformDirty = false;
					}
					else if (mTopVersion != version)
					{
						mMatrix = *top & mInnerMatrix;
					}
				}
				mTopVersion = version;
			}

		private:
			int mTopVersion = -1;
			Mat4 mInnerMatrix{};
		};

		using NodeWithMatrix2D = NodeWithMatrix_<Transform2D_>;
		using NodeWithMatrix3D = NodeWithMatrix_<Transform3D_>;
		using NodeWithOverMatrix2D = NodeWithOverMatrix_<Transform2D_>;
		using NodeWithOverMatrix3D = NodeWithOverMatrix_<Transform3D_>;
	} // namespace Game
} // namespace Zen
